
module jade.tpl.js.Jade;

import jade.tpl.Template ;

final class DJade_Prototype : Dobject {
	

	final this(Dobject prototype){
		super(prototype);
		
		static enum NativeFunctionData nfd[] =
		[
			{"compile", &Jade_prototype_compile, 0} ,
			{"source", &Jade_prototype_source, 0} ,
		];

		DnativeFunction.init(this, nfd, DontEnum | DontDelete | ReadOnly );
	}
	
	static void* Jade_prototype_source(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] argv){
		static const func_name = "jade.source";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
                	return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		ret.putVstring(_this.tpl_source);
		return null ;
	}
	
	
	static void* Jade_prototype_compile(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] argv){
		static const func_name = "jade.compile";

		ErrInfo errinfo;
		if( argv.length < 1 || !argv[0].isString ) {
                	return RuntimeError(&errinfo, "missing arguments " , func_name);
		}
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
                	return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		
		_this._filename	= argv[0].toString.idup ;
	
		try{
			_this.compile1;
			
		}catch(Exception err){
                	return RuntimeError(&errinfo, err.toString , func_name);
		}
		return null ;
	}
}

private enum CType {
	None ,
	String,
	Var,
	Code ,
	String2,
}

final class DJade_Object : Dobject {
	Compiler	compiler ;
	DBuffer_Object	buffer ;
	Dobject		fn1, fn2 ;
	
	string		_filename ;
	SysTime 	_lastModified;
	vBuffer		_bu ;
	CType		_ctype ;
	size_t		_source_end, _fn1_end, _fn2_end ;
	
	final override void* Call(CallContext *cc, Dobject othis, Value* ret, Value[] args){
		
		if( args.length is 0 ) {
			ret.putVstring( cast(string) _bu.slice );
			return null ;
		}
		
		Value* ty	= &args[0] ;
		Value* end	= &args[$-1] ;
		while( ty < end ) {
			scope(exit){
				ty	+= 2 ;
			}
			Value* v = ty+1;
			if( ty.vtype is V_NUMBER ){
				int _ty = ty.toUint32 ;
				if( _ty is 4 ) {
					finish_compile(CType.Code);
					_bu( v.toString ) ;
					_ctype	= CType.Code ;
				} else {
					switch( _ty ) {
						case 0:
							finish_compile(CType.String);
							if( _ctype !is CType.String ){
								_bu("\n\t4,'");
								_ctype	= CType.String ;
							}
							_bu.unstrip(v.toString);
							_ctype	= CType.String ;
							break;
						case 1:
							finish_compile(CType.String);
							if( _ctype !is CType.String ){
								_bu("\n\t4,'");
								_ctype	= CType.String ;
							}
							_bu.unstrip(v.toString);
							_ctype	= CType.String ;
							break;
						case 2:
							finish_compile(CType.Var);
							if( _ctype !is CType.Var ){
								_ctype	= CType.Var ;
							}
							_bu("\n\t5,")(v.toString);
							_ctype	= CType.Var ;
							break;
						case 3:
							finish_compile(CType.Var);
							if( _ctype !is CType.Var ){
								_ctype	= CType.Var ;
							}
							_bu.move(-1);
							_bu("\n\t6,")(v.toString);
							_ctype	= CType.Var ;
						case 5:
							finish_compile(CType.String2);
							_bu(v.toString);
							_ctype	= CType.String2 ;
							break;
						case 6:
							finish_compile(CType.String2);
							_bu.unstrip(v.toString);
							_ctype	= CType.String2 ;
							break;
						default:
							assert(false, ty.toString);
					}
				}
			} else {
				assert(false, ty.getType);
			}
		}
		ret.putVobject(this);
		return null;
	}
	
	package final void finish_compile(CType _ty){
		if( _ty != _ctype ) {
			if( _ty is CType.String || _ty is CType.Var ) {
				if( _ctype != CType.String && _ctype != CType.Var ){
					_bu("\t$(") ;
				} else {
					if( _ctype is CType.String ){
						_bu("'") ;
					}
					_bu(",") ;
				}
			} else {
				if( _ty is CType.Code || _ty is CType.None ){
					if( _ctype is CType.String ||  _ctype is CType.Var ){
						if(  _ctype is CType.String ) {
							_bu("'") ;
						}
						_bu(");\n") ;
					}
				}
			}
		}
	}
	
	
	final this(Dobject prototype){
		super(prototype);
		
		static enum NativeFunctionData nfd[] =
		[
			{"each", &Jade_prototype_each, 0} ,
		];
		
		DnativeFunction.init(this, nfd, DontEnum | DontDelete | ReadOnly );
	}
	

	package final void initialize(Value[] args) {
		buffer	= new DBuffer_Object(buffer_prototype);
		int len 	= 1024 * 256 ;
		int step 	= 1024 * 512 ;
		if( args !is null && args.length > 0 && args[0].isNumber ) {
			len	= cast(int) args[0].toInteger ;
			if( len < 512 || len > 102400 ) {
				len	=  1024 ;
			}
			if( args.length > 1 && args[1].isNumber ) {
				step	= cast(int) args[1].toInteger ;
				if( step < 512 || step > 102400 ) {
					step	=  1024 ;
				}
			}
		}
		_bu		= new vBuffer(len, step);
		buffer.bu	= _bu ;
		Put("buffer",  buffer, DontDelete | ReadOnly | DontEnum ) ;
	}
	
	final typeof(this) setTplSourceFile(string filename){
		_filename	= filename.idup ;
		return this ;
	}
	
	final string tpl_source() in {
		assert( _source_end <= _bu.length );
	} out {
		assert( _source_end <= _bu.length );
	} body {
		return cast(string) _bu.slice[ 0 .. _source_end  ] ;
	}
	
	final string fn1_body() in {
		assert( _source_end <= _bu.length );
		assert( _fn1_end > _source_end );
	} out {
		assert( _source_end <= _bu.length );
		assert( _fn1_end > _source_end );
	}body{
		if( _fn1_end !is 0 ) {
			return cast(string) _bu.slice[ _source_end .. _fn1_end ] ;
		}
		return cast(string) _bu.slice[ _source_end .. $ ] ;
	}
	
	final string fn2_body() in {
		assert( _fn1_end <= _bu.length );
	} out {
		assert( _fn1_end <= _bu.length );
	}body{
		if( _fn1_end < _fn1_end || _fn2_end > _bu.length ) {
			return null ;
		}
		return cast(string) _bu.slice[ _fn1_end .. _fn2_end ] ;
	}
	
	final typeof(this) compile1(){

		if( !exists(_filename) ){
			Log("`%s` is not exists", _filename);
			assert(false);
		}
		SysTime _ts	= timeLastModified(_filename);
		if( _ts == _lastModified ){
			return this;
		}
		_lastModified	= _ts ;
		this._bu.clear();
		_source_end = 0;
		_fn1_end	= 0 ;
		_fn2_end	= 0 ;
		fn1	= null ;
		fn2	= null ;
		
		auto _file	= new  std.stream.File(  this._filename ) ;
		scope(exit) _file.close;
		
		while(true){
			ptrdiff_t len 	= this._bu.length  ;
			ptrdiff_t step	= 1024 * 32 ;
			this._bu.move(step);
			ubyte[] _file_tmp =  this._bu.slice[len..$];
			ptrdiff_t i	= _file.readBlock(_file_tmp.ptr, _file_tmp.length);
			scope(exit){
				if( i !is step ) {
					this._bu.move( i - step ) ;
				}
			}
			if( _file.eof) {
				break ;
			}
			assert( i < 0 || i > step );
		}
		_source_end	= this._bu.length ;
		
		StopWatch sw;
		sw.start;
		this._bu("with(tpl){\n");
		compiler.Init(_filename, cast(string) this._bu.slice[0.._source_end] );
		compiler.compile ;
		_ctype	= CType.None ;
		this._bu( compiler.js );
		//this._bu( this.jade.compile(this.options.filename) ) ;
		this._bu("\n}\n");
		_fn1_end	= this._bu.length ;
		
		sw.stop;
		// Log("jade.source compile %dms", sw.peek.msecs);
		sw.reset;
		sw.start;
		
		Value _ret ;
		Value[2] _args ;
		_args[0].putVstring("tpl");
		_args[1].putVstring( cast(string) this._bu.slice[_source_end..$] );
		Value* _pret	= cast(Value*) Dfunction_constructor.Construct(Tpl_Factory.program.callcontext, &_ret, _args);
		if( _pret !is null ) {
			Log("jade.fn1 new error `%s`  on line : %d", _pret.toString , Tpl_Factory.program.callcontext.linnum);
			Log("%s", fn1_body);
			assert(false);
		}
		if( !_ret.isObject ){
			assert(false);
		}
		sw.stop;
		// Log("jade.fn1 new %dms", sw.peek.msecs);
		
		fn1	= _ret.toObject ;
		return this ;
	}
	
	final typeof(this) compile2(Dobject vars, bool _recompile = false ) in {
		assert(_source_end !is 0);
		assert(_fn1_end > _source_end);
		assert(fn1 !is null);
		assert(_bu !is null);
	} body {
		if( _recompile is false && _fn2_end !is 0 && _fn2_end > _fn1_end && fn2 !is null ){
			return this ;
		}
		_fn2_end	= 0 ;
		fn2	= null ;
		
		StopWatch sw;
		sw.start;
		_ctype	= CType.None ;
		Value	ret ;
		Value[1] args ;
		args[0].putVobject(vars);
		this._bu("with(tpl){\n");
		Value* _pret =  cast(Value*) fn1.Call(Tpl_Factory.program.callcontext,this, &ret, args);
		if( _pret !is null ) {
			Log("jade.fn1 call error `%s`  on line : %d", _pret.toString , Tpl_Factory.program.callcontext.linnum);
			Log("%s", fn1_body);
			assert(false);
		}
		finish_compile(CType.None);
		this._bu("\n}\n");
		_fn2_end	= this._bu.length ;
		
		sw.stop;
		// Log("jade.fn1 call %dms", sw.peek.msecs);
		sw.reset;
		sw.start;
		
		Value _ret ;
		Value[2] _args ;
		_args[0].putVstring("tpl");
		_args[1].putVstring(fn2_body);
		_pret	= cast(Value*) Dfunction_constructor.Construct(Tpl_Factory.program.callcontext, &_ret, _args);
		if( _pret !is null ) {
			Log("jade.fn2 new error `%s`  on line : %d", _pret.toString , Tpl_Factory.program.callcontext.linnum);
			Log("%s", fn2_body);
			assert(false);
		}
		if( !_ret.isObject ){
			assert(false);
		}
		fn2	= _ret.toObject ;
		sw.stop;
		// Log("jade.fn2 new %dms", sw.peek.msecs);
		return this ;
	}
	
	final string render(Dobject vars, bool _recompile = false ) {
		
		vars.Put("$", this, DontEnum | DontDelete | ReadOnly );
		
		compile1 ;
		compile2(vars, _recompile) ;
		
		StopWatch sw;
		sw.start;
		
		_ctype	= CType.None ;
		Value _ret ;
		Value[1] _args ;
		_args[0].putVobject(vars);
		Value* _pret	= cast(Value*) fn2.Call(Tpl_Factory.program.callcontext,this, &_ret, _args);
		if( _pret !is null ) {
			Log("jade.fn2 call error `%s`  on line : %d", _pret.toString , Tpl_Factory.program.callcontext.linnum);
			Log("%s", fn2_body);
			assert(false);
		}
		finish_compile(CType.None) ;
		sw.stop;
		// Log("jade.fn2 call %dms", sw.peek.msecs);
		
		string _html =	cast(string) _bu.slice[ _fn2_end .. $] ;
		_bu.move( 0 - _html.length );
		return _html ;
	}
	
	final typeof(this) destory(){
		fn1	= null ;
		fn2	= null ;
		this._bu.clear;
		_source_end	= 0 ;
		_fn1_end		= 0 ;
		_fn2_end		= 0 ;
		return this ;
	}
	
	/++
	static void* Jade_prototype_finish(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] argv){
		static const func_name = "jade.finish";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
                	return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		if( _this._isString ) {
			_this._bu("');\n");
			_this._isString	= false ;
		}
		ret.putVobject(_this) ;
		return null ;
	}
	
	// strip , unstrip, write, code, each
	static void* Jade_prototype_strip(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] args){
		static const func_name = "jade.strip";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
			return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		if( ! _this._isString ){
			_this._bu("$.write('");
			_this._isString 	= true ;
		}
		foreach(ref v ;args ){
			switch( v.vtype ){
				case V_NUMBER:
				case V_BOOLEAN:
					_this._bu( v.toString);
					break;
				case V_NULL:
					_this._bu(TEXT_null);
					break;
				case V_UNDEFINED:
					_this._bu(TEXT_undefined);
					break;
				default:
					_this._bu( v.toString);
			}
		}
		ret.putVobject(_this);
		return null ;
	}
	static void* Jade_prototype_unstrip(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] args){
		static const func_name = "jade.strip";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
			return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		if( ! _this._isString ){
			_this._bu("$.write('");
			_this._isString 	= true ;
		}
		foreach(ref v ; args ){
			switch( v.vtype ){
				case V_NUMBER:
				case V_BOOLEAN:
					_this._bu( v.toString);
					break;
				case V_NULL:
					_this._bu(TEXT_null);
					break;
				case V_UNDEFINED:
					_this._bu(TEXT_undefined);
					break;
				default:
					_this._bu.strip(v.toString);
			}
		}
		ret.putVobject(_this);
		return null ;
	}
	static void* Jade_prototype_write(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] args){
		static const func_name = "jade.write";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
			return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		if( ! _this._isString ){
			_this._bu("$.buffer('");
			_this._isString 	= true ;
		}
		foreach(ref v ; args ){
			switch( v.vtype ){
				case V_NUMBER:
				case V_BOOLEAN:
					_this._bu( v.toString);
					break;
				case V_NULL:
					_this._bu(TEXT_null);
					break;
				case V_UNDEFINED:
					_this._bu(TEXT_undefined);
					break;
				default:
					_this._bu.unstrip(v.toString);
			}
		}
		ret.putVobject(_this);
		return null ;
	}
	
	static void* Jade_prototype_code(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] args){
		static const func_name = "jade.code";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
			return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		
		if( _this._isString ){
			_this._bu("');\n");
			_this._isString 	= false ;
		}
		
		foreach(ref v ;args ){
			switch( v.vtype ){
				case V_NUMBER:
				case V_BOOLEAN:
					_this._bu( v.toString);
					break;
				case V_NULL:
					_this._bu(TEXT_null);
					break;
				case V_UNDEFINED:
					_this._bu(TEXT_undefined);
					break;
				case V_STRING:
				default:
					_this._bu( v.toString);
			}
		}
		
		ret.putVobject(_this);
		return null ;
	}
	++/
	
	
	static void* Jade_prototype_each(Dobject pthis, CallContext* cc, Dobject othis, Value* ret, Value[] args){
		static const func_name = "jade.each";
		ErrInfo errinfo;
		DJade_Object _this = cast(DJade_Object) othis ;
		if( _this is null ) {
			return RuntimeError(&errinfo, ERR_TYPE_ERROR , func_name);
		}
		// args[0], vars 
		// args[1],  items 
		// args[2], dg ,
		if( args.length < 3 || !args[0].isObject || !args[1].isObject || !args[2].isObject  ){
			return RuntimeError(&errinfo, "args " , func_name);
		}
		
		Dobject vars 	= args[0].toObject ;
		Dobject dg	 	= args[2].toObject ;
		
		Value[2] 	_args ;
		Value 	_ret ;
		Value* 	_pret ;
		_args[0].putVobject(dg) ;
		_args[1].putVobject(vars) ;
		
		Dobject obj		= args[1].toObject ;
		Value* each_val	= obj.internal_prototype.Get(TEXT_forEach) ;
		if( each_val !is null && each_val.isObject ) {
			DnativeFunction each_native = cast(DnativeFunction) each_val.toObject ;
			if( each_native !is null ) {
				_pret	= cast(Value*) each_native.pcall(obj, Tpl_Factory.program.callcontext, obj, &_ret,  _args);
				assert(_pret is null);
				return null;
			}
		}

		Darray  darray	= cast(Darray) args[1].toObject ;
		if( darray !is null ) {
   			int len = darray.ulength ;
			for(int i =0; i< len; i++){
				Value* _val	= darray.Get(i);
				if( _val !is null ) {
					Value.copy(&_args[0], _val);
					_args[1].putVnumber(i);
					_pret = cast(Value*) dg.Call( Tpl_Factory.program.callcontext, vars, &_ret, _args );
					if( _pret !is null ){
						Log( _pret.toString );
						assert(false);
					}
				}
			}
			return null;
		}
		
		obj.proptable.opApply((ref Value key, ref Property p){
			if( !(p.attributes & DontEnum ) ){
				Value.copy(&_args[0], &p.value);
				Value.copy(&_args[1], &key);
				_pret = cast(Value*) dg.Call( Tpl_Factory.program.callcontext, vars, &_ret, _args );
				if( _pret !is null ){
					Log( _pret.toString );
					assert(false);
				}
			}
			return 0; 
		});
		 
		return null ;
	}
}




